import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.apache.mahout.common.RandomUtils;
import org.jblas.DoubleMatrix;

import de.mlp_distributed.mlp.math.function.DoubleDoubleFunction;
import de.mlp_distributed.mlp.math.jblas.JDenseMatrix;
import de.mlp_distributed.mlp.math.jblas.JDenseVector;
import de.mlp_distributed.mlp.math.mahout.DenseMatrix;
import de.mlp_distributed.mlp.math.mahout.DenseVector;
import de.mlp_distributed.mlp.math.mahout.Matrix;
import de.mlp_distributed.mlp.math.mahout.Vector;

public class TestMatrizes {

	private final static int columnSize = 1000;
	private final static int rowSize = 1000;

	static int repeat = 100;

	private static List<Double> jmul = new ArrayList<Double>();
	private static List<Double> mul = new ArrayList<Double>();
	private static List<Double> jadd = new ArrayList<Double>();
	private static List<Double> add = new ArrayList<Double>();
	private static List<Double> jassign = new ArrayList<Double>();
	private static List<Double> assign = new ArrayList<Double>();

	/**
	 * @param args
	 */
	public static void main(final String[] args) {
		RandomUtils.useTestSeed();
		final Random gen = RandomUtils.getRandom();

		final double[][] matrix = TestMatrizes.constructMatrixArray(gen);

		final double[] vector = TestMatrizes.constructVectorArray(gen);

		final DenseVector dv = new DenseVector(vector);
		final DoubleMatrix jv = new DoubleMatrix(vector);

		final DoubleMatrix jdm = new DoubleMatrix(TestMatrizes.constructMatrixArray(gen));
		final DoubleMatrix jdm1 = new DoubleMatrix(TestMatrizes.constructMatrixArray(gen));

		final DenseMatrix dm = new DenseMatrix(TestMatrizes.constructMatrixArray(gen));
		final DenseMatrix dm2 = new DenseMatrix(TestMatrizes.constructMatrixArray(gen));

		final JDenseMatrix jdm2 = new JDenseMatrix(TestMatrizes.constructMatrixArray(gen));
		final JDenseMatrix jdm3 = new JDenseMatrix(TestMatrizes.constructMatrixArray(gen));
		final JDenseVector jdv = new JDenseVector(vector);

		long startTime = 0;
		for (int i = 0; i < TestMatrizes.repeat; i++) {
			// startTime = System.nanoTime();
			// final DoubleMatrix jresult = jdm.mmul(jv);
			// System.out.println("jblas multiply " + ((System.nanoTime() -
			// startTime) / 100000) + " seconds");
			//
			// // startTime = System.currentTimeMillis();
			// // final DoubleMatrix jresult2 = jdm.muliRowVector(jv);
			// // System.out.println("jblas multiply2 " +
			// // ((System.currentTimeMillis() - startTime) / 1000.0) +
			// // " seconds");

			startTime = System.nanoTime();
			final Vector mresult = dm.times(dv);
			final double time1 = (System.nanoTime() - startTime) / 100000;
			TestMatrizes.mul.add(time1);

			// startTime = System.currentTimeMillis();
			// final Vector mt_result = mt_dm.times(dv);
			// System.out.println("mt_mahout multiply " +
			// ((System.currentTimeMillis() - startTime) / 1000.0) +
			// " seconds");

			startTime = System.nanoTime();
			final Vector jdmt_result = jdm2.times(jdv);
			final double time2 = (System.nanoTime() - startTime) / 100000;
			TestMatrizes.jmul.add(time2);

		}

		for (int i = 0; i < TestMatrizes.repeat; i++) {
			// startTime = System.nanoTime();
			// final DoubleMatrix jresult = jdm.add(jdm1);
			// System.out.println("jblas add " + ((System.nanoTime() -
			// startTime) / 100000) + " seconds");

			// startTime = System.currentTimeMillis();
			// final DoubleMatrix jresult2 = jdm.muliRowVector(jv);
			// System.out.println("jblas multiply2 " +
			// ((System.currentTimeMillis() - startTime) / 1000.0) +
			// " seconds");

			startTime = System.nanoTime();
			final Matrix mresult = dm.plus(dm2);
			final double time1 = (System.nanoTime() - startTime) / 100000;
			TestMatrizes.add.add(time1);

			// startTime = System.currentTimeMillis();
			// final Vector mt_result = mt_dm.times(dv);
			// System.out.println("mt_mahout multiply " +
			// ((System.currentTimeMillis() - startTime) / 1000.0) +
			// " seconds");

			startTime = System.nanoTime();
			final Matrix jdmt_result = jdm2.plus(jdm3);
			final double time2 = (System.nanoTime() - startTime) / 100000;
			TestMatrizes.jadd.add(time2);
		}

		for (int i = 0; i < TestMatrizes.repeat; i++) {
			// startTime = System.currentTimeMillis();
			// final DoubleMatrix jresult2 = jdm.muliRowVector(jv);
			// System.out.println("jblas multiply2 " +
			// ((System.currentTimeMillis() - startTime) / 1000.0) +
			// " seconds");

			startTime = System.nanoTime();
			final Matrix mresult = dm.assign(dm2, new DoubleDoubleFunction() {
				@Override
				public double apply(final double a, final double b) {
					return a + (0.001 * b);
				}
			});
			final double time1 = (System.nanoTime() - startTime) / 100000;
			TestMatrizes.assign.add(time1);

			// startTime = System.currentTimeMillis();
			// final Vector mt_result = mt_dm.times(dv);
			// System.out.println("mt_mahout multiply " +
			// ((System.currentTimeMillis() - startTime) / 1000.0) +
			// " seconds");

			startTime = System.nanoTime();
			final Matrix jdmt_result = jdm2.assign(jdm3, new DoubleDoubleFunction() {
				@Override
				public double apply(final double a, final double b) {
					return a + (0.001 * b);
				}
			});
			final double time2 = (System.nanoTime() - startTime) / 100000;
			TestMatrizes.jassign.add(time2);

		}
		System.out.println("col size" + TestMatrizes.columnSize);
		System.out.println("row size" + TestMatrizes.rowSize);
		TestMatrizes.computeStandardDeviation(TestMatrizes.mul, "Mahout-Mul: ");
		TestMatrizes.computeStandardDeviation(TestMatrizes.add, "Mahout-Add: ");
		TestMatrizes.computeStandardDeviation(TestMatrizes.assign, "Mahout-assign: ");
		TestMatrizes.computeStandardDeviation(TestMatrizes.jmul, "JBlas-Mul: ");
		TestMatrizes.computeStandardDeviation(TestMatrizes.jadd, "JBlas-Add: ");
		TestMatrizes.computeStandardDeviation(TestMatrizes.jassign, "JBlas-assign: ");
		TestMatrizes.testCross();
	}

	static void testCross() {
		final List<Double> cross = new ArrayList<Double>();
		final List<Double> jcross = new ArrayList<Double>();

		final double[] vector = SpeedTestMahout.constructVectorArray(TestMatrizes.columnSize);

		final JDenseVector jdv = new JDenseVector(vector.clone());
		final DenseVector dv = new DenseVector(vector.clone());

		final JDenseVector jtarget = new JDenseVector(vector.clone());
		final DenseVector target = new DenseVector(vector.clone());
		long startTime = 0;
		for (int i = 0; i < TestMatrizes.repeat; i++) {
			startTime = System.nanoTime();
			final Matrix result = jdv.cross(jtarget);
			final double time1 = (System.nanoTime() - startTime) / 100000;
			jcross.add(time1);

			startTime = System.nanoTime();
			final Matrix result2 = dv.cross(target);
			final double time2 = (System.nanoTime() - startTime) / 100000;
			cross.add(time2);
		}
		TestMatrizes.computeStandardDeviation(cross, "Mahout-cross: ");
		TestMatrizes.computeStandardDeviation(jcross, "JBlas-cross: ");
	}

	private static double[] constructVectorArray(final Random gen) {
		final double[] returnVal = new double[TestMatrizes.columnSize];
		for (int j = 0; j < TestMatrizes.columnSize; j++) {
			double w = 0;
			w = 1.73d / Math.sqrt(TestMatrizes.columnSize);
			w = ((2.0 * gen.nextDouble()) - 1.0) * w;
			returnVal[j] = w;
		}
		return returnVal;
	}

	static double[][] constructMatrixArray(final Random gen) {
		final double[][] returnVal = new double[TestMatrizes.rowSize][TestMatrizes.columnSize];
		final int fanIn = TestMatrizes.columnSize;
		final int fanOut = TestMatrizes.rowSize;
		for (int i = 0; i < fanOut; i++) {
			for (int j = 0; j < fanIn; j++) {
				double w = 0;
				if (i != 0) {
					w = 1.73d / Math.sqrt(fanIn);
					w = ((2.0 * gen.nextDouble()) - 1.0) * w;
				}
				returnVal[i][j] = w;
			}
		}
		return returnVal;
	}

	private static double computeStandardDeviation(final List<Double> resultList, final String name) {
		if (resultList.size() == 0) {
			return 0;
		}
		double average = 0;
		for (int i = 0; i < resultList.size(); i++) {
			average += resultList.get(i);
		}
		average = average / resultList.size();

		double sum = 0;
		for (int j = 0; j < resultList.size(); j++) {
			final Double er = resultList.get(j);
			sum += Math.pow(er - average, 2);
		}
		sum = (sum) / (resultList.size() - 1);
		final double stdDev = Math.sqrt(sum);

		final double unsecurity = TestMatrizes.computeUnsecurity(stdDev, resultList.size());

		System.out.println(name + " average: " + average + " stdDev: " + stdDev + " unsecurity: " + unsecurity);

		return stdDev;
	}

	private static double computeUnsecurity(final double standardDeviation, final int n) {
		final double unsecurity = standardDeviation / Math.sqrt(n);
		return unsecurity;
	}
}
